package datamining;

import java.io.*;
import java.text.DecimalFormat;
import java.util.*;

public class Apriori  {
    // the list of current itemsets 
    private List<int[]> cursets; 
    // the name of the data file 
    private String dataFile;
    // number of different items in the dataset 
    private int numItems;
    // total number of records in transaFile 
    private int numRecords;
    // minimum support percentage
    private double minSup;
    // minmimum conf
    private double minConf;
    //input file to a number list
    private int[][] inputInt;
    //flag to decide whether it is first time to output
    public boolean ouputFlag = true;
    //a key to name mapping
    public HashMap keyToName = new HashMap<String,String>();
    //a minSup set with its support value
    public HashMap minSupSet = new HashMap<String, Integer>();

    
    //generate min-support set
    public void Apriori(String[] args) throws Exception{
        initial(args);
        findMinSup();
    }
    //main function
    private void findMinSup() throws Exception {
        createInitialSet();
        int itemsetNumber = 1;

        while (cursets.size() > 0) {
            getFrq();
            if (cursets.size() != 0) {
                createNewSet();
            }
            itemsetNumber++;
        }

        FindOutRules fr = new FindOutRules(keyToName,minConf);
        fr.AssociationRules(minSupSet,numRecords);
    }

    //output the result
    private void outPut(int[] itemset, int support) {
        String outputFile = "output.txt";
        String s = "";
        String[] output= new String[itemset.length];
        
        //for each element, put them into result HashMap
        for (int i = 0; i < itemset.length; i++) {
            s += itemset[i] + " ";
            String key = String.valueOf(itemset[i]);
            output[i] = keyToName.get(key).toString();
        }
        s = s.substring(0, s.length() - 1);
        minSupSet.put(s, support);
        
        DecimalFormat df = new DecimalFormat(); 
        df.setMaximumFractionDigits(2); 
        if (ouputFlag == true)
             System.out.println("==Large itemsets (min_sup="+minSup*100 + "%)");
        System.out.println(Arrays.toString(output) + "  (" + df.format((support / (double) numRecords)*100) +"%)");

        try {
            FileWriter fw;
            String out="";
            String title="==Large itemsets (min_sup="+minSup*100 + "%) \n";
            if (ouputFlag == true) {
                ouputFlag = false;
                fw = new FileWriter(outputFile);
                out= title + Arrays.toString(output) + "  (" + df.format((support / (double) numRecords)*100) +"%)" + "\n";
            } else {
                fw = new FileWriter(outputFile, true);
               out= Arrays.toString(output) + "  (" + df.format((support / (double) numRecords)*100) +  "%)" + "\n";

            }
            BufferedWriter file_out = new BufferedWriter(fw);
            //file_out.newLine();
            file_out.write(out);
            //file_out.write("-\n");
            file_out.close();
        } catch (IOException e) {
            System.out.println(e);
        }
    }

    //Initial system args, get the input data 
    private void initial(String[] args) throws Exception {
        // setting transafile
        if (args.length != 0) {
            dataFile = args[0];
        } else {
            dataFile = "nyc.csv"; // default
        }
        // setting minsupport
        if (args.length >= 2) {
            minSup = (Double.valueOf(args[1]).doubleValue());
            minConf= (Double.valueOf(args[2]).doubleValue());
        } else {
            minSup = 0.3;}// by default
            if (minSup > 1 || minSup < 0) {
                throw new Exception("minSup: bad value");
            }
            // going thourgh the file to compute numItems and  numTransactions
            numItems = 0;
            numRecords = 0;
            //read the input file
            File file = new File(dataFile);
            BufferedReader reader = null;
            List<String[]> slist = new ArrayList<String[]>();

            reader = new BufferedReader(new FileReader(file));
            String tempString = null;
            //read the file into a list
            while ((tempString = reader.readLine()) != null) {
                StringTokenizer tokens;
                String word;
                int i = 0;
                //read the string split by" ,"
                tokens = new StringTokenizer(tempString, ",");
                String[] symbol = new String[4];
                while (tokens.hasMoreTokens()) {
                    word = tokens.nextToken();
                    word.trim();
                    symbol[i] = word;
                    i++;
                }
                slist.add(symbol);
            }
            reader.close();
            //set number of number of records
            numRecords = slist.size() - 1;
            //numRecords = 10000;
            //establish a mapping fron name to number
            HashMap<String, Integer> nameToKey = new HashMap<String, Integer>();
            int count = 0;
            for (int i = 1; i < numRecords; i++) {
                String[] tmpLine = slist.get(i);
                for (int j = 0; j < 4; j++) {
                    String name = tmpLine[j];
                    if (nameToKey.get(name) == null) {
                        nameToKey.put(name, count);
                        count++;
                    }
                }
            }
            numItems = count++;
            inputInt = new int[numRecords][4];
            for (int i = 1; i < numRecords; i++) {
                for (int j = 0; j < 4; j++) {
                    inputInt[i][j] = nameToKey.get(slist.get(i)[j]);
                }
            }
            //eatablish mapping from number to name
            keyToName = new HashMap<String, String>();
            Set data = nameToKey.entrySet();
            Iterator i = data.iterator();
            while (i.hasNext()) {
                Map.Entry me = (Map.Entry) i.next();
                keyToName.put(me.getValue().toString(), me.getKey().toString());
            }
        }
    
    
    //Initial single element set
    private void createInitialSet() {
        cursets = new ArrayList<int[]>();
        //put all single item as candidate
        for (int i = 0; i < numItems; i++) {
            int[] cand = {i};
            cursets.add(cand);
        }
    }
   
    //create new set 
    private void createNewSet() {
        int currentSize = cursets.get(0).length;
        HashMap<String, int[]> newCandSet = new HashMap<String, int[]>();
        //use 2 loop to compare each pair of itemsets of size n-1
        for (int i = 0; i < cursets.size(); i++) {
            for (int j = i + 1; j < cursets.size(); j++) {
                int[] set1 = cursets.get(i);
                int[] set2 = cursets.get(j);
                boolean newsetflag = false;
                //initial potenial new set
                int[] newCand = new int[currentSize + 1];
                for (int k = 0; k < newCand.length - 1; k++) {
                    newCand[k] = set1[k];
                }
                // check whether set2 already existed
                for (int idx1 = 0; idx1 < set2.length; idx1++) {
                    boolean found = false;
                    for (int idx2 = 0; idx2 < set1.length; idx2++) {
                        if (set1[idx2] == set2[idx1]) {
                            found = true;
                            break;
                        }
                    }
                    //if not, generate a new candidate set
                    if (!found) {
                        newsetflag = true;
                        newCand[newCand.length - 1] = set2[idx1];
                    }
                }
                //put the new set into the haspmap
                if (newsetflag) {
                    Arrays.sort(newCand);
                    newCandSet.put(Arrays.toString(newCand), newCand);
                }
            }
        }
        //update the new current itemsets
        cursets = new ArrayList<int[]>(newCandSet.values());
    }
    
    //computer the Frequency of each candidate
    private void getFrq() throws Exception {
        //the frequent for the current itemset
        List<int[]> freCand = new ArrayList<int[]>();
        //if current set appear in a record
        boolean match;
        int count[] = new int[cursets.size()];
        //a Matrix to determine whether a set of item exist in a line
        boolean[] idxMatrix = new boolean[numItems];

        // for each record
        for (int i = 1; i < numRecords; i++) {
            Arrays.fill(idxMatrix, false);
            //fill the matrix by input file
            for (int j = 0; j < 4; j++) {
                idxMatrix[inputInt[i][j]] = true;
            }
            //for each item in itemset 
            for (int c = 0; c < cursets.size(); c++) {
                match = true;
                int[] cand = cursets.get(c);
                for (int idxc : cand) {
                    if (idxMatrix[idxc] == false) {
                        match = false;
                        break;
                    }
                }
                if (match) {
                    count[c]++;
                }
            }
        }
        //compute the support
        for (int i = 0; i < cursets.size(); i++) {
            if ((count[i] / (double) (numRecords)) >= minSup) {
                outPut(cursets.get(i), count[i]);
                freCand.add(cursets.get(i));
            }
        }
        //update itemset
        cursets = freCand;
    }
    

}
